package cn.melonlib.graphql.cores.comp.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.melonlib.graphql.annos.*;
import cn.melonlib.graphql.exceptions.GraphqlDataException;
import cn.melonlib.graphql.exceptions.GraphqlParseException;
import cn.melonlib.graphql.model.enums.GraphqlFunctionMode;
import cn.melonlib.graphql.model.enums.GraphqlScalarType;
import cn.melonlib.graphql.model.enums.GraphqlTypeMode;
import cn.melonlib.graphql.model.metadata.*;
import cn.melonlib.objects.maps.DataMap;
import cn.melonlib.objects.maps.JsonMap;
import graphql.schema.GraphQLScalarType;
import lombok.Getter;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

@Component
@Getter
public class GraphqlScanner extends ApplicationObjectSupport {

    @Value("${graphql.config.default_namespace:default}")
    public String defaultNamespace;

//    @Value("${graphql.scan.packages:cn.melonlib.graphql}")
//    private List<String> packages;

    private DataMap<Object, GraphqlScalarType> scalars=new DataMap<>();
    {
        Arrays.stream(GraphqlScalarType.values()).forEach(s->{
            scalars.append(s.getScalarType().getName(),s);
            scalars.append(s.getJavaType().getSimpleName(),s);
            scalars.append(s.name(),s);
        });
    }

    private final JsonMap<GraphqlView> allViewTypes=JsonMap.createMap();
    private final JsonMap<GraphqlInput> allInputTypes=JsonMap.createMap();

    private final JsonMap<JsonMap<GraphqlView>> viewTypes=JsonMap.<JsonMap<GraphqlView>>createMap();
    private final JsonMap<JsonMap<GraphqlInput>> inputTypes=JsonMap.<JsonMap<GraphqlInput>>createMap();

    private final JsonMap<JsonMap<GraphqlTypeSuper>> types=JsonMap.createMap();

    private final JsonMap<JsonMap<GraphqlFunction>> functions=JsonMap.createMap();

    private final JsonMap<JsonMap<GraphqlFunction>> queries=JsonMap.createMap();

    private final JsonMap<JsonMap<GraphqlFunction>> mutations=JsonMap.createMap();



    public void scanFunctions() {
        this.getApplicationContext().getBeansWithAnnotation(GraphqlSchema.class).entrySet().stream().distinct().forEach(e->{
            String beanName=e.getKey();
            Object bean=e.getValue();
            Class<?> c= AopUtils.getTargetClass(bean);
            GraphqlSchema graphqlSchema=AnnotationUtils.findAnnotation(c,GraphqlSchema.class);
            List<String> namespaces= Arrays.stream(graphqlSchema.namespace()).toList();
            if(CollectionUtil.isEmpty(namespaces)){
                namespaces=List.of(defaultNamespace);
            }
            namespaces.forEach(n->{
                try {
                    List<GraphqlFunction> graphqlFunctions = getAllFunction(n,c, beanName);
                    graphqlFunctions.stream().forEach(f -> {
                        JsonMap<GraphqlFunction> functionJsonMap = functions.get(n, JsonMap::createMap);
                        if (functionJsonMap.containsKey(f.getName())) {
                            throw new RuntimeException("存在两个相同的函数名：【" + c.getName() + "#" + f.getName() + "]");
                        }
                        functionJsonMap.append(f.getName(), f);
                        if (Objects.equals(f.getMode(), GraphqlFunctionMode.QUERY)) {
                            queries.get(n, JsonMap::createMap).append(f.getName(), f);
                        }
                        if (Objects.equals(f.getMode(), GraphqlFunctionMode.MUTATION)) {
                            mutations.get(n, JsonMap::createMap).append(f.getName(), f);
                        }
                    });
                }catch (Exception ex){
                    throw new RuntimeException(ex.getMessage(),ex);
                }
            });

        });
    }

    public void scanType(List<String> packages) {
        packages.stream().flatMap(p-> ClassUtil.scanPackageByAnnotation(p,GraphqlType.class).stream())
                .forEach(c->{
                    GraphqlType graphqlType=AnnotationUtils.findAnnotation(c,GraphqlType.class);
                    GraphqlTypeDeclare [] declares=graphqlType.declares();

                    List<String> namespaces=new ArrayList<>(Arrays.stream(graphqlType.namespaces()).toList());
                    if(CollectionUtil.isEmpty(namespaces)){
                        namespaces.add(defaultNamespace);
                    }
                    if(namespaces.contains("*")){
                        for (int i = 0; i < declares.length; i++) {
                            GraphqlTypeDeclare declare=declares[i];
                            String name=declare.name();
                            name=StringUtils.isNotBlank(name)?name:c.getSimpleName();
                            switch (declare.mode()){
                                case VIEW :{
                                    try {
                                        List<GraphqlFieldBean> fields = getAllField(c, GraphqlTypeMode.VIEW);
                                        if (allViewTypes.keySet().contains(name)) {
                                            throw new GraphqlParseException("*:"+"重复视图名称的类型：" + name,graphqlType);
                                        }
                                        GraphqlView graphqlView = new GraphqlView(name, StringUtils.defaultIfBlank(graphqlType.comment(), c.getName()), fields);
                                        allViewTypes.append(name, graphqlView);
                                    }catch (Exception e){
                                        throw new RuntimeException(e.getMessage(),e);
                                    }
                                    break;
                                }
                                case INPUT:{
                                    try {
                                        List<GraphqlFieldBean> fields = getAllField(c, GraphqlTypeMode.INPUT);
                                        if (allInputTypes.keySet().contains(name)) {
                                            throw new GraphqlParseException("重复输入名称的类型：" + name,graphqlType);
                                        }
                                        GraphqlInput graphqlInput = new GraphqlInput(name, StringUtils.defaultIfBlank(graphqlType.comment(), c.getName()), fields);
                                        allInputTypes.append(name, graphqlInput);
                                    }catch (Exception e){
                                        throw  new RuntimeException(e.getMessage(),e);
                                    }
                                    break;
                                }
                                default:{
                                    throw new RuntimeException("不支持的类型");
                                }
                            }
                        }
                    }
                    namespaces.stream().filter(n->!Objects.equals(n,"*")).forEach(n->{
                        for (int i = 0; i < declares.length; i++) {
                            GraphqlTypeDeclare declare=declares[i];
                            String name=declare.name();
                            name=StringUtils.isNotBlank(name)?name:c.getSimpleName();
                            JsonMap<GraphqlTypeSuper> supers=types.get(n,JsonMap::createMap);
                            switch (declare.mode()){
                                case VIEW :{
                                    try {
                                        List<GraphqlFieldBean> fields = getAllField(c, GraphqlTypeMode.VIEW);
                                        JsonMap<GraphqlView> views = viewTypes.get(n, JsonMap::createMap);
                                        if (views.keySet().contains(name)) {
                                            throw new GraphqlParseException("命名空间："+n+":重复视图名称的类型：" + name,graphqlType);
                                        }
                                        GraphqlView graphqlView = new GraphqlView(name, StringUtils.defaultIfBlank(graphqlType.comment(), c.getName()), fields);
                                        views.append(name, graphqlView);
                                        supers.append(name, graphqlView);
                                    }catch (Exception e){
                                        throw new RuntimeException(e.getMessage(),e);
                                    }
                                    break;
                                }
                                case INPUT:{
                                    try {
                                        List<GraphqlFieldBean> fields = getAllField(c, GraphqlTypeMode.INPUT);
                                        JsonMap<GraphqlInput> inputs = inputTypes.get(n, JsonMap::createMap);
                                        if (inputs.keySet().contains(name)) {
                                            throw new GraphqlParseException("命名空间：" + n + "重复输入名称的类型：" + name, graphqlType);
                                        }
                                        GraphqlInput graphqlInput = new GraphqlInput(name, StringUtils.defaultIfBlank(graphqlType.comment(), c.getName()), fields);
                                        inputs.append(name, graphqlInput);
                                        supers.append(name, graphqlInput);
                                    }catch (Exception e){
                                        throw new RuntimeException(e.getMessage(),e);
                                    }
                                    break;
                                }
                                default:{
                                    throw new RuntimeException("不支持的类型");
                                }
                            }
                        }
                    });
                });
    }

    private List<GraphqlFieldBean> getAllField(Class<?> type, GraphqlTypeMode mode) throws GraphqlParseException{
        List<GraphqlFieldBean> fields=new ArrayList<>();
        List<String> valids=new ArrayList<>();
        Class<?> curType=type;
        while (!Objects.equals(curType,Object.class)){
            List<GraphqlFieldBean> temp=Arrays.stream(curType.getDeclaredFields()).filter(f->!valids.contains(f.getName()))
                    .map(f-> {
                        try {
                            return this.getFieldBean(f,mode);
                        } catch (GraphqlParseException e) {
                            throw new RuntimeException(e.getMessage(),e);
                        }
                    }).filter(Objects::nonNull).toList();
            fields.addAll(temp);
            valids.addAll(temp.stream().map(GraphqlFieldBean::getName).toList());
            curType=curType.getSuperclass();
        }
        return fields.stream().toList();
    }

    protected GraphqlFieldBean getFieldBean(Field field,GraphqlTypeMode mode) throws GraphqlParseException{
        GraphqlField graphqlField=AnnotationUtils.findAnnotation(field,GraphqlField.class);
        if(Objects.nonNull(graphqlField)&&graphqlField.ignore()){
            return null;
        }
        boolean list=false;
        Class<?> type=field.getType();
        if(Collection.class.isAssignableFrom(type)){
            type=ResolvableType.forField(field).getGeneric(0).toClass();
            list=true;
        }
        try {
            if (Objects.nonNull(graphqlField)) {
                return new GraphqlFieldBean(StringUtils.defaultIfBlank(graphqlField.name(), field.getName())
                        , getType(type, mode), graphqlField.comment(), graphqlField.required(), graphqlField.requiredBody(), list
                );
            } else {
                return new GraphqlFieldBean(field.getName(), getType(type, mode), field.getName(), false, false, list);
            }
        }catch (Exception e){
            throw new GraphqlParseException(field.getName()+":("+e.getMessage()+")",e,mode);
        }
    }


    protected List<GraphqlFunction> getAllFunction(String namespace,Class<?> c,String beanName) throws GraphqlParseException{
        List<Method> methods=new ArrayList<>();
        Class<?> tmp= c;
        GraphqlSchema schema=AnnotationUtils.findAnnotation(c,GraphqlSchema.class);
        while (!Objects.equals(tmp,Object.class)){
            methods.addAll(Arrays.stream(tmp.getDeclaredMethods()).toList());
            tmp=tmp.getSuperclass();
        }
        try {
            return methods.stream().map(m -> {
                try {
                    return getFunction(namespace,m, beanName, schema);
                } catch (GraphqlParseException e) {
                    throw new RuntimeException(e.getMessage(),e);
                }
            }).filter(Objects::nonNull).toList();
        }catch (Exception e){
            throw new GraphqlParseException(beanName+":("+e.getMessage()+")",e,schema);
        }
    }


    protected GraphqlFunction getFunction(String namespace,Method m,String beanName,GraphqlSchema schema) throws GraphqlParseException{
        Class<?> returnType=m.getReturnType();
        Class<?> declareType=m.getDeclaringClass();
        String prefix=switch (schema.value()){
            case NONE -> "";
            case CUSTOM -> schema.prefix()+schema.separator();
            case TYPE_NAME -> StrUtil.lowerFirst(declareType.getSimpleName())+schema.separator();
        };
        GraphqlQuery graphqlQuery=AnnotationUtils.findAnnotation(m,GraphqlQuery.class);
        GraphqlMutation graphqlMutation=AnnotationUtils.findAnnotation(m,GraphqlMutation.class);
        if(Objects.nonNull(graphqlQuery)){
            GraphqlFunction graphqlFunction=new GraphqlFunction();
            graphqlFunction.setMode(GraphqlFunctionMode.QUERY);
            graphqlFunction.setName(prefix+StringUtils.defaultIfBlank(graphqlQuery.name(),m.getName()));
            graphqlFunction.setQueryMode(graphqlQuery.queryMode());
            graphqlFunction.setComment(graphqlQuery.comment());
            graphqlFunction.setBean(beanName);
            graphqlFunction.setMethod(m);
            try {
                if (Collection.class.isAssignableFrom(returnType)) {
                    Class<?> inner = ResolvableType.forMethodReturnType(m).getGeneric(0).toClass();
                    String t=getType(inner, GraphqlTypeMode.VIEW);
                    if(validType(namespace,t,GraphqlTypeMode.VIEW)) {
                        graphqlFunction.setType(t);
                    }else{
                        throw new GraphqlParseException("命名空间"+namespace+"未定义返回类型："+t,graphqlFunction);
                    }
                    graphqlFunction.setList(true);
                } else {
                    String t=getType(returnType, GraphqlTypeMode.VIEW);
                    if(validType(namespace,t,GraphqlTypeMode.VIEW)) {
                        graphqlFunction.setType(getType(returnType, GraphqlTypeMode.VIEW));
                    }else{
                        throw new GraphqlParseException("命名空间"+namespace+"未定义返回类型："+t,graphqlFunction);
                    }
                    graphqlFunction.setList(false);
                }
                graphqlFunction.setArguments(Arrays.stream(m.getParameters()).reduce(new ArrayList<>(), (l, p) -> {
                    try {
                        l.add(getArgument(namespace,m, p, l.size()));
                        return l;
                    }catch (Exception e){
                        throw new RuntimeException(e.getMessage(),e);
                    }
                }, (l, p) -> null));
                return graphqlFunction;
            }catch (Exception exception){
                throw new GraphqlParseException(graphqlFunction.getName()+":("+ exception.getMessage()+")",exception,graphqlFunction);
            }
        }else if(Objects.nonNull(graphqlMutation)){
            GraphqlFunction graphqlFunction=new GraphqlFunction();
            graphqlFunction.setMode(GraphqlFunctionMode.MUTATION);
            graphqlFunction.setName(prefix+StringUtils.defaultIfBlank(graphqlMutation.name(),m.getName()));
            graphqlFunction.setComment(graphqlMutation.comment());
            graphqlFunction.setBean(beanName);
            graphqlFunction.setMethod(m);
            try {
                if (Collection.class.isAssignableFrom(returnType)) {
                    Class<?> inner = ResolvableType.forMethodReturnType(m).getGeneric(0).toClass();
                    graphqlFunction.setType(getType(inner, GraphqlTypeMode.VIEW));
                    graphqlFunction.setList(true);
                } else {
                    graphqlFunction.setType(getType(returnType, GraphqlTypeMode.VIEW));
                    graphqlFunction.setList(false);
                }
                graphqlFunction.setArguments(Arrays.stream(m.getParameters()).reduce(new ArrayList<>(), (l, p) -> {
                    try {
                        l.add(getArgument(namespace,m, p, l.size()));
                        return l;
                    }catch (Exception e){
                        throw new RuntimeException(e.getMessage(),e);
                    }
                }, (l, p) -> null));
                return graphqlFunction;
            }catch (Exception e){
                throw new GraphqlParseException(graphqlFunction.getName()+":("+e.getMessage()+")",e,graphqlFunction);
            }
        }else{
            return null;
        }
    }


    protected GraphqlArgument getArgument(String namespace,Method m,Parameter parameter,int index) throws GraphqlParseException{
        Class<?> type = parameter.getType();
        GraphqlParameter graphqlParameter = AnnotationUtils.findAnnotation(parameter, GraphqlParameter.class);
        GraphqlArgument graphqlArgument = new GraphqlArgument();

        if (Objects.nonNull(graphqlParameter)) {
            graphqlArgument.setName(StringUtils.defaultIfBlank(graphqlParameter.value(), parameter.getName()));
            graphqlArgument.setRequired(graphqlParameter.required());
            graphqlArgument.setRequiredBody(graphqlParameter.requiredBody());
            graphqlArgument.setList(Collection.class.isAssignableFrom(type));
            graphqlArgument.setComment(graphqlParameter.comment());
        } else {
            graphqlArgument.setName(parameter.getName());
            graphqlArgument.setRequired(false);
            graphqlArgument.setRequiredBody(false);
            graphqlArgument.setList(Collection.class.isAssignableFrom(type));
            graphqlArgument.setComment(parameter.getName());
        }
        try {
            if (Collection.class.isAssignableFrom(type)) {
                Class<?> inner = ResolvableType.forMethodParameter(m, index).getGeneric(0).toClass();
                String t=getType(inner, GraphqlTypeMode.INPUT);
                if(validType(namespace,t,GraphqlTypeMode.INPUT)) {
                    graphqlArgument.setType(t);
                }else{
                    throw new GraphqlParseException("命名空间"+namespace+"未定参数义类型："+t,graphqlArgument);
                }
            } else {
                String t=getType(type, GraphqlTypeMode.INPUT);
                if(validType(namespace,t,GraphqlTypeMode.INPUT)) {
                    graphqlArgument.setType(t);
                }else{
                    throw new GraphqlParseException("命名空间" + namespace + "未定义参数类型：" + t,graphqlArgument);
                }
            }
            return graphqlArgument;
        }catch (GraphqlParseException ex){
            throw new GraphqlParseException(graphqlArgument.getName()+"["+index+"]:("+ex.getMessage()+")",ex,graphqlArgument);
        }
    }

    protected String getType(Class<?> type,GraphqlTypeMode mode) throws GraphqlParseException{
        String typeName=type.getName();
        return switch (typeName){
            case "int"->GraphqlScalarType.INT.getScalarType().getName();
            case "java.lang.Integer"->GraphqlScalarType.INTEGER.getScalarType().getName();
            case "float"->GraphqlScalarType.FLOAT_TYPE.getScalarType().getName();
            case "java.lang.Float"->GraphqlScalarType.FLOAT.getScalarType().getName();
            case "long"->GraphqlScalarType.LONG_TYPE.getScalarType().getName();
            case "java.lang.Long"->GraphqlScalarType.LONG.getScalarType().getName();
            case "double"->GraphqlScalarType.DOUBLE_TYPE.getScalarType().getName();
            case "java.lang.Double"->GraphqlScalarType.DOUBLE.getScalarType().getName();
            case "boolean"->GraphqlScalarType.BOOLEAN_TYPE.getScalarType().getName();
            case "java.lang.Boolean"->GraphqlScalarType.BOOLEAN.getScalarType().getName();
            case "java.lang.String"->GraphqlScalarType.STRING.getScalarType().getName();
            default -> {
                if(type.isEnum()){
                    yield GraphqlScalarType.STRING.getScalarType().getName();
                }
                if(Map.class.isAssignableFrom(type)){
                    yield GraphqlScalarType.JSON.getScalarType().getName();
                }
                if(Collection.class.isAssignableFrom(type)) {
                    throw new GraphqlParseException(typeName + ":graphql定义类型不允许为集合类型",mode);
                }
                GraphqlType graphqlType=AnnotationUtils.findAnnotation(type,GraphqlType.class);
                if(Objects.isNull(graphqlType)){
                    throw new GraphqlParseException(typeName+":未定义为Graphql类型:"+type.getName());
                }
                GraphqlTypeDeclare [] graphqlTypeDeclares=graphqlType.declares();
                for (int i = 0; i < graphqlTypeDeclares.length; i++) {
                    if(Objects.equals(graphqlTypeDeclares[i].mode(),mode)){
                        yield StringUtils.defaultIfBlank(graphqlTypeDeclares[i].name(),type.getSimpleName());
                    }
                }

                throw new GraphqlParseException("未找到对应的定义类型:"+type.getName()+"--["+mode.name()+":"+mode.getLabel()+"]",mode);
            }
        };
    }

    public boolean validType(String namespace,String type,GraphqlTypeMode mode){

        return switch (mode){
            case VIEW -> this.scalars.containsKey(type)||this.allViewTypes.containsKey(type)||
                    (this.viewTypes.containsKey(namespace)&&this.viewTypes.get(namespace).containsKey(type)&& Objects.nonNull(this.viewTypes.get(namespace).get(type)));
            case INPUT -> this.scalars.containsKey(type)||this.allInputTypes.containsKey(type)||
                    (this.inputTypes.containsKey(namespace)&&this.inputTypes.get(namespace).containsKey(type)&&Objects.nonNull(this.inputTypes.get(namespace).get(type)));
        };
    }

}
